﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Collections.Generic;

namespace Microsoft.Research.DynamicDataDisplay.Charts
{
    internal abstract class TimePeriodTicksProvider<T> : ITicksProvider<T>
    {
        public event EventHandler Changed;
        protected void RaiseChanged()
        {
            if (Changed != null)
            {
                Changed(this, EventArgs.Empty);
            }
        }

        protected abstract T RoundUp(T time, DifferenceIn diff);
        protected abstract T RoundDown(T time, DifferenceIn diff);

        private bool differenceInited = false;
        private DifferenceIn difference;
        protected DifferenceIn Difference
        {
            get
            {
                if (!differenceInited)
                {
                    difference = GetDifferenceCore();
                    differenceInited = true;
                }
                return difference;
            }
        }
        protected abstract DifferenceIn GetDifferenceCore();

        private int[] tickCounts = null;
        protected int[] TickCounts
        {
            get
            {
                if (tickCounts == null)
                    tickCounts = GetTickCountsCore();
                return tickCounts;
            }
        }
        protected abstract int[] GetTickCountsCore();

        public int DecreaseTickCount(int ticksCount)
        {
            if (ticksCount > TickCounts[0]) return TickCounts[0];

            for (int i = 0; i < TickCounts.Length; i++)
                if (ticksCount > TickCounts[i])
                    return TickCounts[i];

            return TickCounts.Last();
        }

        public int IncreaseTickCount(int ticksCount)
        {
            if (ticksCount >= TickCounts[0]) return TickCounts[0];

            for (int i = TickCounts.Length - 1; i >= 0; i--)
                if (ticksCount < TickCounts[i])
                    return TickCounts[i];

            return TickCounts.Last();
        }

        protected abstract int GetSpecificValue(T start, T dt);
        protected abstract T GetStart(T start, int value, int step);
        protected abstract bool IsMinDate(T dt);
        protected abstract T AddStep(T dt, int step);

        public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
        {
            T start = range.Min;
            T end = range.Max;
            DifferenceIn diff = Difference;
            start = RoundDown(start, end);
            end = RoundUp(start, end);

            RoundingInfo bounds = RoundHelper.CreateRoundedRange(
                GetSpecificValue(start, start),
                GetSpecificValue(start, end));

            int delta = (int)(bounds.Max - bounds.Min);
            if (delta == 0)
                return new TicksInfo<T> { Ticks = new T[] { start } };

            int step = delta / ticksCount;

            if (step == 0) step = 1;

            T tick = GetStart(start, (int)bounds.Min, step);
            bool isMinDateTime = IsMinDate(tick) && step != 1;
            if (isMinDateTime)
                step--;

            List<T> ticks = new List<T>();
            T finishTick = AddStep(range.Max, step);
            while (Continue(tick, finishTick))
            {
                ticks.Add(tick);
                tick = AddStep(tick, step);
                if (isMinDateTime)
                {
                    isMinDateTime = false;
                    step++;
                }
            }

            ticks = Trim(ticks, range);

            TicksInfo<T> res = new TicksInfo<T> { Ticks = ticks.ToArray(), Info = diff };
            return res;
        }

        protected abstract bool Continue(T current, T end);

        protected abstract T RoundUp(T start, T end);

        protected abstract T RoundDown(T start, T end);

        protected abstract List<T> Trim(List<T> ticks, Range<T> range);

        public ITicksProvider<T> MinorProvider
        {
            get { throw new NotSupportedException(); }
        }

        public ITicksProvider<T> MayorProvider
        {
            get { throw new NotSupportedException(); }
        }
    }

    internal abstract class DatePeriodTicksProvider : TimePeriodTicksProvider<DateTime>
    {
        protected sealed override bool Continue(DateTime current, DateTime end)
        {
            return current < end;
        }

        protected sealed override List<DateTime> Trim(List<DateTime> ticks, Range<DateTime> range)
        {
            int startIndex = 0;
            for (int i = 0; i < ticks.Count - 1; i++)
            {
                if (ticks[i] <= range.Min && range.Min <= ticks[i + 1])
                {
                    startIndex = i;
                    break;
                }
            }

            int endIndex = ticks.Count - 1;
            for (int i = ticks.Count - 1; i >= 1; i--)
            {
                if (ticks[i] >= range.Max && range.Max > ticks[i - 1])
                {
                    endIndex = i;
                    break;
                }
            }

            List<DateTime> res = new List<DateTime>(endIndex - startIndex + 1);
            for (int i = startIndex; i <= endIndex; i++)
            {
                res.Add(ticks[i]);
            }

            return res;
        }

        protected sealed override DateTime RoundUp(DateTime start, DateTime end)
        {
            bool isPositive = (end - start).Ticks > 0;
            return isPositive ? SafelyRoundUp(end) : RoundDown(end, Difference);
        }

        private DateTime SafelyRoundUp(DateTime dt)
        {
            if (AddStep(dt, 1) == DateTime.MaxValue)
                return DateTime.MaxValue;

            return RoundUp(dt, Difference);
        }

        protected sealed override DateTime RoundDown(DateTime start, DateTime end)
        {
            bool isPositive = (end - start).Ticks > 0;
            return isPositive ? RoundDown(start, Difference) : SafelyRoundUp(start);
        }

        protected sealed override DateTime RoundDown(DateTime time, DifferenceIn diff)
        {
            DateTime res = time;

            switch (diff)
            {
                case DifferenceIn.Year:
                    res = new DateTime(time.Year, 1, 1);
                    break;
                case DifferenceIn.Month:
                    res = new DateTime(time.Year, time.Month, 1);
                    break;
                case DifferenceIn.Day:
                    res = time.Date;
                    break;
                case DifferenceIn.Hour:
                    res = time.Date.AddHours(time.Hour);
                    break;
                case DifferenceIn.Minute:
                    res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute);
                    break;
                case DifferenceIn.Second:
                    res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
                    break;
                case DifferenceIn.Millisecond:
                    res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second).AddMilliseconds(time.Millisecond);
                    break;
                default:
                    break;
            }

            DebugVerify.Is(res <= time);

            return res;
        }

        protected override DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
        {
            DateTime res = RoundDown(dateTime, diff);

            switch (diff)
            {
                case DifferenceIn.Year:
                    res = res.AddYears(1);
                    break;
                case DifferenceIn.Month:
                    res = res.AddMonths(1);
                    break;
                case DifferenceIn.Day:
                    res = res.AddDays(1);
                    break;
                case DifferenceIn.Hour:
                    res = res.AddHours(1);
                    break;
                case DifferenceIn.Minute:
                    res = res.AddMinutes(1);
                    break;
                case DifferenceIn.Second:
                    res = res.AddSeconds(1);
                    break;
                case DifferenceIn.Millisecond:
                    res = res.AddMilliseconds(1);
                    break;
                default:
                    break;
            }

            return res;
        }
    }
}
